package com.cyy.view

import cn.hutool.core.io.FileUtil
import com.cyy.model.Conss
import com.cyy.model.GmodelModel
import com.cyy.srv.GenSrv
import com.jfinal.kit.Kv
import com.jfinal.template.Engine
import javafx.geometry.Orientation
import javafx.scene.control.ContextMenu
import javafx.scene.control.MenuItem
import javafx.scene.control.TextArea
import javafx.scene.layout.Priority
import javafx.stage.FileChooser
import org.fxmisc.richtext.CodeArea
import org.fxmisc.richtext.LineNumberFactory
import org.fxmisc.richtext.model.StyleSpans
import org.fxmisc.richtext.model.StyleSpansBuilder
import tornadofx.*
import java.io.File
import java.time.Duration
import java.util.*
import java.util.regex.Pattern

/**Enjoy模板练习
 * d:\Program\idea\jre64\bin\java.exe -jar build\libs\EnjoySqlite.jar
 */
class GEnjoy(val gmodel: GmodelModel, val genSrv: GenSrv) : View() {
    val engine = gmodel.engine.value
    var kv = mutableMapOf<String, String>()
    var kv1 = Kv.create()

    val strOut = stringProperty()
    // Template String TextArea
    lateinit var ta: TextArea
    val mi1 = MenuItem("渲染当前行")
    val mi2 = MenuItem("渲染选中")
    val mi3 = MenuItem("渲染所有")
    val mClear = MenuItem("清除")
    val ca = CodeArea()

    override val root = scrollpane(true, true) {
        form {
            vgrow = Priority.ALWAYS
            fieldset {
                field {
                    button("选择模板文件") {
                        //                        addClass(Styles.tackyButton)
                        action {
                            val efset = arrayOf(FileChooser.ExtensionFilter("选择模板文件", "*.*"))
                            val fnset = chooseFile("选择模板文件", efset, FileChooserMode.Single) {
                                // p初始目录为当前项目目录
                                initialDirectory = File(File("").canonicalPath)
                            }

                            if (fnset.isNotEmpty()) {
                                gmodel.engineTemplateFilePath.value = fnset.first().toString()
                                gmodel.tplString.value = FileUtil.readUtf8String(fnset.first())
                                ca.replaceText(gmodel.tplString.value)
                            }
                        }
                    }
                    textfield(gmodel.engineTemplateFilePath) {
                        promptText = "engine template file path"
                    }
                    label("SharedObjects")
                    combobox<Any> {
                        items = observableListOf(
                                Conss.getSharedObjectMap(gmodel.engine.value).toString().trimStart('{').trimEnd('}').split(",")
                        )
                    }
                }
                separator(Orientation.HORIZONTAL)
                field {
                    button("渲染模板字符串") {
                        enableWhen(gmodel.tplString.isNotBlank())
                        action {
                            try {
                                strOut.value = engine.getTemplateByString(gmodel.tplString.value).renderToString(kv)
                            } catch (e: Exception) {
                                strOut.value = e.toString()
                            }
                        }
                    }
                    button("渲染选择的模板字符串") {
                        enableWhen(gmodel.tplString.isNotBlank())
                        action {
                            try {
                                val tplString = ta.selectedText
                                strOut.value = engine.getTemplateByString(tplString).renderToString(kv)
                            } catch (e: Exception) {
                                strOut.value = e.toString()
                            }
                        }
                    }

                    button("清空模板缓存") {
                        action {
                            engine.removeAllTemplateCache()
                        }
                    }
                    button("保存结果到文件") {
                        enableWhen(strOut.isNotBlank())
                        action {
                            try {
                                val efset = arrayOf(FileChooser.ExtensionFilter("save文件name", "*.*"))
                                val fnset = chooseFile("选择文件path", efset, FileChooserMode.Single) {
                                    initialDirectory = File(File("").canonicalPath)
                                }
                                if (fnset.isNotEmpty()) {
                                    FileUtil.writeUtf8String(strOut.value, fnset.first())
                                }

                            } catch (e: Exception) {
                                strOut.value = e.toString()
                            }
                        }
                    }
                }
            }

            hbox(10) {
                fieldset(labelPosition = Orientation.VERTICAL) {
                    field("Template String") {
                        //                        ta = textarea(gmodel.tplString) {
//                            prefHeight = 600.0
//                            style {
//                                fontSize = 16.px
//                            }
//                            required()
//                            contextmenu {
//                                item("渲染选中") {
//                                    action {
//                                        strOut.value = engine.getTemplateByString(ta.selectedText).renderToString(kv)
//                                    }
//                                }
//                                item("渲染所有") {
//                                    action {
//                                        strOut.value = engine.getTemplateByString(ta.text).renderToString(kv)
//                                    }
//                                }
//                                item("渲染当前行") {
//                                    action {
//                                        strOut.value = engine.getTemplateByString(ta.selectedText).renderToString(kv)
//                                    }
//                                }
//                            }
//                            text = """
//                                #(1+2/3*4)
//                                #(1>2)
//                                #("abcdef".substring(0,3))
//                                #set(arr=[1,2,3])
//                                #(arr[1])
//                                #set(menu='index')
//                                #(menu=='index' ? 'current' : 'normal')
//                                #(menu!="index" ? "current" : "normal")
//                                #if(menu=='index')
//                                    #(menu)
//                                #end
//                                #if(menu!='index')
//                                    #(normal)
//                                #else
//                                    #(menu)
//                                #end
//
//                                #set(map={k1:123,"k2":"abc", "k3":gmodel})
//                                #(map.k1)
//                                #(map.k2)
//                                #(map["k1"])
//                                #(map["k2"])
//                                #(map.get("k1"))
//                                #({1:'自买', 2:'跟买'}.get(1))
//                                #({1:'自买', 2:'跟买'}[2])
//
//                                ### 与双问号符联合使用支持默认值
//                                #({1:'自买', 2:'跟买'}.get(999) ?? '其它')
//
//                                // 定义数组 array，并为元素赋默认值
//                                #set(array = [123, "abc", true])
//                                #set(array2 = [2123, "2abc", false])
//
//                                // 获取下标为 1 的值，输出为: "abc"
//                                #(array[1])
//
//                                // 将下标为 1 的元素赋值为 false，并输出
//                                #(array[1] = false, array[1])
//                                ###set(array = [ 123, "abc", true, a && b || c, 1 + 2, obj.doIt(x) ])
//
//                                #for(x : [1..10])
//                                   #(x)
//                                #end
//
//                                // 对 Map 进行迭代
//                                #for(x : map)
//                                  #(x.key)
//                                  #(x.value)
//                                #end
//
//                                // 对 List、数组、Set 这类结构进行迭代
//                                #for(x : array)
//                                   #(for.size)
//                                   #(for.index)
//                                   #(for.count)
//                                   #(for.first)
//                                   #(for.last)
//                                   #(for.odd)
//                                   #(for.even)
//
//                                  #for(x : array2)
//                                     #(for.outer)
//                                     #(for.outer.index)
//                                     #(for.index)
//                                  #end
//                                #end
//                                #for(month:[1..12])
//                                    #switch (month)
//                                      #case (1, 3, 5, 7, 8, 10, 12)
//                                        #(month) 月有 31 天
//                                      #case (2)
//                                        #(month) 月平年有28天，闰年有29天
//                                      #default
//                                        月份错误: #(month ?? "null")--#(month) 月平年有30天
//                                    #end
//                                #end
//                                #(gmodel.baseResourcesPath ?? "gmodel is not exist")
//                            """.trimIndent()
//                        }
                        add(ca)
                    }
                }

                separator(Orientation.VERTICAL)
                fieldset("渲染结果") {
                    textarea(strOut) {
                        prefHeight = 600.0
//                        required()
                        isWrapText = true
                        isEditable = false
                        style {
                            fontSize = 16.px
                        }
                    }
                }
            }
        }
    }

    init {
        prepareCA(ca)
    }

    fun prepareCA(ca: CodeArea) {
        ca.setParagraphGraphicFactory(LineNumberFactory.get(ca))
        ca.isAutoScrollOnDragDesired = true
        ca.prefWidth = 800.0
        ca.prefHeight = 600.0
        ca.style {
            fontSize = 16.px
        }
//        mi1.setAccelerator(KeyCombination.valueOf("SHIFT+1"))
        mi1.setOnAction {
            // 渲染当前行内容
//            println(ca.getText(ca.currentParagraph))
//            strOut.value = engine.getTemplateByString(ca.getText(ca.currentParagraph)).renderToString(kv)
            strOut.value = renderStr(ca.getText(ca.currentParagraph), kv1)
        }
        mi2.setOnAction {
            //            println(ca.selectedText)
//            strOut.value = engine.getTemplateByString(ca.selectedText).renderToString(kv)
            strOut.value = renderStr(ca.selectedText, kv1)
        }
        mi3.setOnAction {
            //            println(ca.text)
//            strOut.value = engine.getTemplateByString(ca.text).renderToString(kv)
            strOut.value = renderStr(ca.text, kv1)
        }
        mClear.setOnAction {
            ca.replaceText("")
        }

        ca.contextMenu = ContextMenu(mi1)
        ca.contextMenu.items.addAll(mi2, mi3, mClear)
        var cleanupWhenNoLongerNeedIt = ca

                // plain changes = ignore style changes that are emitted when syntax highlighting is reapplied
                // multi plain changes = save computation by not rerunning the code multiple times
                //   when making multiple changes (e.g. renaming a method at multiple parts in file)
                .multiPlainChanges()

                // do not emit an event until 500 ms have passed since the last emission of previous stream
                .successionEnds(Duration.ofMillis(500))

                // run the following code block when previous stream emits an event
                .subscribe({ _ -> ca.setStyleSpans(0, computeHighlighting(ca.text)) })
        ca.replaceText(0, 0, sampleCode)

    }

    fun renderStr(inStr: String, kv: Kv): String {
        return engine.getTemplateByString(inStr).renderToString(kv)
    }
}

// jfinal enjoy keywords : "#","set","end"
private val KEYWORDS = arrayOf("#", "set", "end", "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while")
private val KEYWORD_PATTERN = "\\b(" + KEYWORDS.joinToString("|") + ")\\b"
private val PAREN_PATTERN = "\\(|\\)"
private val BRACE_PATTERN = "\\{|\\}"
private val BRACKET_PATTERN = "\\[|\\]"
private val SEMICOLON_PATTERN = "\\;"
private val STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\""
private val COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/"

val PATTERN = Pattern.compile(
        "(?<KEYWORD>" + KEYWORD_PATTERN + ")"
                + "|(?<PAREN>" + PAREN_PATTERN + ")"
                + "|(?<BRACE>" + BRACE_PATTERN + ")"
                + "|(?<BRACKET>" + BRACKET_PATTERN + ")"
                + "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
                + "|(?<STRING>" + STRING_PATTERN + ")"
                + "|(?<COMMENT>" + COMMENT_PATTERN + ")"
)

//private val sampleCode = arrayOf("package com.example;", "", "import java.util.*;", "", "public class Foo extends Bar implements Baz {", "", "    /*", "     * multi-line comment", "     */", "    public static void main(String[] args) {", "        // single-line comment", "        for(String arg: args) {", "            if(arg.length() != 0)", "                System.out.println(arg);", "            else", "                System.err.println(\"Warning: empty string as argument\");", "        }", "    }", "", "}").joinToString("\n")
private val sampleCode = """
                                #(1+2/3*4)
                                #(1>2)
                                #("abcdef".substring(0,3))
                                #set(arr=[1,2,3])
                                #(arr[1])
                                #set(menu='index')
                                #(menu=='index' ? 'current' : 'normal')
                                #(menu!="index" ? "current" : "normal")
                                #if(menu=='index')
                                    #(menu)
                                #end
                                #if(menu!='index')
                                    #(normal)
                                #else
                                    #(menu)
                                #end

                                #set(map={k1:123,"k2":"abc", "k3":gmodel})
                                #(map.k1)
                                #(map.k2)
                                #(map["k1"])
                                #(map["k2"])
                                #(map.get("k1"))
                                #({1:'自买', 2:'跟买'}.get(1))
                                #({1:'自买', 2:'跟买'}[2])

                                ### 与双问号符联合使用支持默认值
                                #({1:'自买', 2:'跟买'}.get(999) ?? '其它')

                                // 定义数组 array，并为元素赋默认值
                                #set(array = [123, "abc", true])
                                #set(array2 = [2123, "2abc", false])

                                // 获取下标为 1 的值，输出为: "abc"
                                #(array[1])

                                // 将下标为 1 的元素赋值为 false，并输出
                                #(array[1] = false, array[1])
                                ###set(array = [ 123, "abc", true, a && b || c, 1 + 2, obj.doIt(x) ])

                                #for(x : [1..10])
                                   #(x)
                                #end

                                // 对 Map 进行迭代
                                #for(x : map)
                                  #(x.key)
                                  #(x.value)
                                #end

                                // 对 List、数组、Set 这类结构进行迭代
                                #for(x : array)
                                   #(for.size)
                                   #(for.index)
                                   #(for.count)
                                   #(for.first)
                                   #(for.last)
                                   #(for.odd)
                                   #(for.even)

                                  #for(x : array2)
                                     #(for.outer)
                                     #(for.outer.index)
                                     #(for.index)
                                  #end
                                #end
                                #for(month:[1..12])
                                    #switch (month)
                                      #case (1, 3, 5, 7, 8, 10, 12)
                                        #(month) 月有 31 天
                                      #case (2)
                                        #(month) 月平年有28天，闰年有29天
                                      #default
                                        月份错误: #(month ?? "null")--#(month) 月平年有30天
                                    #end
                                #end
                                #(gmodel.baseResourcesPath ?? "gmodel is not exist")
                            """.trimIndent()

private fun computeHighlighting(text: String): StyleSpans<Collection<String>> {
    val PATTERN = Pattern.compile(
            "(?<KEYWORD>" + KEYWORD_PATTERN + ")"
                    + "|(?<PAREN>" + PAREN_PATTERN + ")"
                    + "|(?<BRACE>" + BRACE_PATTERN + ")"
                    + "|(?<BRACKET>" + BRACKET_PATTERN + ")"
                    + "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
                    + "|(?<STRING>" + STRING_PATTERN + ")"
                    + "|(?<COMMENT>" + COMMENT_PATTERN + ")"
    )
    val matcher = PATTERN.matcher(text)
    var lastKwEnd = 0
    val spansBuilder = StyleSpansBuilder<Collection<String>>()
    while (matcher.find()) {
        val styleClass = (if (matcher.group("KEYWORD") != null)
            "keyword"
        else if (matcher.group("PAREN") != null)
            "paren"
        else if (matcher.group("BRACE") != null)
            "brace"
        else if (matcher.group("BRACKET") != null)
            "bracket"
        else if (matcher.group("SEMICOLON") != null)
            "semicolon"
        else if (matcher.group("STRING") != null)
            "string"
        else if (matcher.group("COMMENT") != null)
            "comment"
        else
            null)!! /* never happens */
        spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd)
        spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start())
        lastKwEnd = matcher.end()
    }
    spansBuilder.add(Collections.emptyList(), text.length - lastKwEnd)
    return spansBuilder.create()
}